/*
 * Matthew Cramer, Jesse Martinez, Marc Mendoza 
 * CSE 325 Lab 3
 * File: ProcessManager.c
 * Description: Manager that handles the Queue interactions and processes.  
 *              This is called from the testing interface to perform 
 *              transitions to the queues.
 *
 */

#include "../QueueManager/QueueManager.h"
#include "../Scheduler/Scheduler.h"
#define MAX_PROCESSES 20
#define MAX_QUEUE_SIZE 20
#define MAX_RUNNING_SIZE 1

process_control_block tempQueue[1];

int totalProcesses = 0;
int pid=0;

//Create Ready queue and its head and tail pointers
process_control_block ready[20];
int readyHead = -1;
int readyTail = -1;
int readyFreeHead = 0;
int readyFreeTail = MAX_QUEUE_SIZE-1;
int readySize = 0;

//Create Running queue and its head and tail pointers
process_control_block running[1];
int runningHead = -1;
int runningTail = -1;
int runningFreeHead = 0;
int runningFreeTail = MAX_RUNNING_SIZE-1;
int runningSize=0;

//Create Wait queue and its head and tail pointers
process_control_block wait[20];
int waitHead = -1;
int waitTail = -1;
int waitFreeHead = 0;
int waitFreeTail = MAX_QUEUE_SIZE-1;
int waitSize = 0;

//Create Terminate queue and its head and tail pointers
process_control_block terminate[20];
int terminateHead = -1;
int terminateTail = -1;
int terminateFreeHead = 0;
int terminateFreeTail = MAX_QUEUE_SIZE-1;
int terminateSize = 0;

process_control_block* getQueue(int);
int getHead(int);
int getTail(int);
int newProcess(int, int, int, int, int, int);
int goFunction(int, int);
int unwaitFunction(int);
int eoquantumFunction();
int eolifeFunction();
int waitFunction();
int processManagerFunction(int, int);
void initializeQueues();
void clearQueues();
int getSizeOfQueue(int);
void starvationFunction();

//Initializes Free Lists for queues
void initializeQueues()
{
    iniFree(&readyFreeHead, &readyFreeTail, ready);
	iniFree(&runningFreeHead, &runningFreeTail, running);
    iniFree(&waitFreeHead, &waitFreeTail, wait);
    iniFree(&terminateFreeHead, &terminateFreeTail, terminate);
	pid = 0;
}

//Initializes Queues for testing purposes
void clearQueues()
{
    iniQueue(&readyHead, &readyTail, &readyFreeHead, &readyFreeTail, &readySize, ready, MAX_QUEUE_SIZE);
    iniQueue(&runningHead, &runningTail, &runningFreeHead, &runningFreeTail, &runningSize, running, MAX_RUNNING_SIZE);
    iniQueue(&waitHead, &waitTail, &waitFreeHead, &waitFreeTail, &waitSize, wait,MAX_QUEUE_SIZE);
    iniQueue(&terminateHead, &terminateTail, &terminateFreeHead, &terminateFreeTail, &terminateSize, terminate,MAX_QUEUE_SIZE);
	initializeQueues();
    initSchedule();
}

//Return queue to Testing Interface
process_control_block* getQueue(int queue)
{
    if(queue==2)
        return ready;
    else if(queue==3)
        return running;
    else if(queue==4)
        return wait;
    else if(queue==5)
        return terminate;
}

//Return head pointer for queue to Testing Interface
int getHead(int queue)
{
    if(queue==2)
        return readyHead;
    else if(queue==3)
        return runningHead;
    else if(queue==4)
        return waitHead;
    else if(queue==5)
        return terminateHead;
}

//Return tail pointer for queue to Testing Interface
int getTail(int queue)
{
     if(queue==2)
        return readyTail;
    else if(queue==3)
        return runningTail;
    else if(queue==4)
        return waitTail;
    else if(queue==5)
        return terminateTail;
}

//Return the size of the queue
int getSizeOfQueue(int queue)
{
    if(queue==2)
        return readySize;
    else if(queue==3)
        return runningSize;
    else if(queue==4)
        return waitSize;
    else if(queue==5)
        return terminateSize;
}

//Modifies the PCB for the scheduler
int modifyPCB(int scheduler)
{
    int returnValue=0;
    //If process is interactive, increase it's priority
    if(scheduler==1)
    {
        if(running[runningHead].priority < 5)
        {
            running[runningHead].priority++;
        }
        running[runningHead].TQ=0;
    }
    //If process is compute bound, decrease its priority
    else if(scheduler==2)
    {
        if(running[runningHead].priority > 1)
        {
            running[runningHead].priority--;
        }
        running[runningHead].TQ=0;
    }
    //If process is still below time quantum, increase TQ
    else
    {
        running[runningHead].TQ++;
    }
}

//Increments Ready time counter for all processes in Ready queue
//This is used if processes begin to starve in the ready queue
//Their priority will increase for every 10 go calls
void starvationFunction()
{
    int returnValue;
    int i=readyHead;
    int j=0;
    while(j<readySize)
    {
        //Increment counter
        ready[i].counter++;
        //If process has been in ready for 10 TQs, increment priority
        //This will help prevent starvation for processes
        if(ready[i].counter == 10)
        {
            if(ready[i].priority < 5)
            {
                ready[i].priority++;
            }
            //Reset the counter again
            ready[i].counter = 0;
        }
        i=ready[i].nextIndex;
        j++;
    }
}

//This function will enqueue new process to ready queue
int newProcess(int psw, int page, int reg0, int reg1, int reg2, int group)
{
    int returnValue = 0;
    //If max processes in system hit, return error
    if(totalProcesses == MAX_PROCESSES)
    {
        returnValue = -8;
    }
    else
    {
        //If ready queue is not full, enqueue
        if(readySize < MAX_QUEUE_SIZE)
        {
			//the following section will reuse pids or create a new one based on a counter
			int tempid;
			//if there are no available pids, the counter is incremented
			//and that is used for the id
            if(terminateSize == 0)
			{
				pid++;
				tempid = pid;
			}
			//otherwise pid is taken from the top of the terminate queue
			else
			{
				tempid = Dequeue(&terminateHead, &terminateTail, &terminateFreeHead, 
					&terminateFreeTail, terminate).pid;
				terminateSize--;
				
				//terminates list is reset if it becomes 0
                if(terminateSize == 0)
                {
                    iniQueue(&terminateHead, &terminateTail, &terminateFreeHead, &terminateFreeTail, &terminateSize, terminate,MAX_QUEUE_SIZE);
                    iniFree(&terminateFreeHead, &terminateFreeTail, terminate);
                }
			}
			//new process is enqueued into the ready queue
            Enqueue(tempid,psw,page,reg0,reg1,reg2,3,0,group,&readyHead, &readyTail, 
                    &readyFreeHead, &readyFreeTail, ready);
            readySize++;
            totalProcesses++;
            returnValue = tempid;
        }
        //If ready queue is full, return error
        else
        {
            returnValue = -1;
        }
    }
    return returnValue;
}

//
int goFunction(int action, int PID)
{
    //If process needs to be put in running state from head of Ready
    if(action == 1)
    {
        tempQueue[0] = Dequeue(&readyHead, &readyTail, &readyFreeHead, &readyFreeTail, ready);
        readySize--;
        if(readySize == 0)
        {
            iniQueue(&readyHead, &readyTail, &readyFreeHead, &readyFreeTail, &readySize, ready, MAX_QUEUE_SIZE);
            iniFree(&readyFreeHead, &readyFreeTail, ready);
        }
        Enqueue(tempQueue[0].pid, tempQueue[0].psw, tempQueue[0].pageTable, tempQueue[0].regs[0],
                tempQueue[0].regs[1], tempQueue[0].regs[2], tempQueue[0].priority, tempQueue[0].TQ,tempQueue[0].group,
                &runningHead, &runningTail, &runningFreeHead, &runningFreeTail, running);   
        runningSize=1;
    }
    //Process for priority needs to be placed in running from ready
    else if(action == 3)
    {
        tempQueue[0] = Delete(PID, &readyHead, &readyTail, &readyFreeHead, &readyFreeTail, &readySize, ready, tempQueue);
        readySize--;
        if(readySize == 0)
        {
            iniQueue(&readyHead, &readyTail, &readyFreeHead, &readyFreeTail, &readySize, ready, MAX_QUEUE_SIZE);
            iniFree(&readyFreeHead, &readyFreeTail, ready);
        }
        Enqueue(tempQueue[0].pid, tempQueue[0].psw, tempQueue[0].pageTable, tempQueue[0].regs[0],
                tempQueue[0].regs[1], tempQueue[0].regs[2], tempQueue[0].priority, tempQueue[0].TQ,tempQueue[0].group,
                &runningHead, &runningTail, &runningFreeHead, &runningFreeTail, running);
        runningSize++;
    }
    return tempQueue[0].pid;
}

//This function will move a process from waiting queue to ready queue
int unwaitFunction(int PID)
{
    tempQueue[0] = Delete(PID, &waitHead, &waitTail, &waitFreeHead, &waitFreeTail, &waitSize, wait, tempQueue);
    
    if(tempQueue[0].pid > 0)
    {
        waitSize--;
        if(waitSize == 0)
        {
            iniQueue(&waitHead, &waitTail, &waitFreeHead, &waitFreeTail, &waitSize, wait,MAX_QUEUE_SIZE);
            iniFree(&waitFreeHead, &waitFreeTail, wait);
        }
        Enqueue(tempQueue[0].pid, tempQueue[0].psw, tempQueue[0].pageTable, tempQueue[0].regs[0],
                tempQueue[0].regs[1], tempQueue[0].regs[2], tempQueue[0].priority, tempQueue[0].TQ,tempQueue[0].group,
                &readyHead, &readyTail, &readyFreeHead, &readyFreeTail, ready);
        readySize++;
    }
    return tempQueue[0].pid;
}

//This function will move a process from running queue to ready queue
int eoquantumFunction()
{
    tempQueue[0] = Dequeue(&runningHead, &runningTail, &runningFreeHead, &runningFreeTail, running);
    runningSize=0;
    Enqueue(tempQueue[0].pid, tempQueue[0].psw, tempQueue[0].pageTable, tempQueue[0].regs[0],
            tempQueue[0].regs[1], tempQueue[0].regs[2], tempQueue[0].priority, tempQueue[0].TQ,tempQueue[0].group,
            &readyHead, &readyTail, &readyFreeHead, &readyFreeTail, ready);
    readySize++;
    return tempQueue[0].pid;
}

//This function will move a process from running queue to terminated queue
int eolifeFunction()
{
    tempQueue[0] = Dequeue(&runningHead, &runningTail, &runningFreeHead, &runningFreeTail, running);
    runningSize=0;
    Enqueue(tempQueue[0].pid, tempQueue[0].psw, tempQueue[0].pageTable, tempQueue[0].regs[0],
            tempQueue[0].regs[1], tempQueue[0].regs[2], tempQueue[0].priority, tempQueue[0].TQ,tempQueue[0].group,
            &terminateHead, &terminateTail, &terminateFreeHead, &terminateFreeTail, terminate);
    terminateSize++;
    totalProcesses--;
    return tempQueue[0].pid;
}

//This function will move a process from running queue to waiting queue
int waitFunction()
{
    tempQueue[0] = Dequeue(&runningHead, &runningTail, &runningFreeHead, &runningFreeTail, running);
    runningSize=0;
    Enqueue(tempQueue[0].pid, tempQueue[0].psw, tempQueue[0].pageTable, tempQueue[0].regs[0],
            tempQueue[0].regs[1], tempQueue[0].regs[2], tempQueue[0].priority, tempQueue[0].TQ,tempQueue[0].group,
            &waitHead, &waitTail, &waitFreeHead, &waitFreeTail, wait);
    waitSize++;
    return tempQueue[0].pid;
}

//This function wil receive the commands from the testing interface
//Returns a PID that has been deleted to be reused 
int processManagerFunction(int command, int PID)
{
    int returnValue = 0;
    
    //Determine what the transition is from Testing Interface
    //1=UNWAIT
    //2=EOQUANTUM
    //3=EOLIFE
    //4=WAIT
    //UNWAIT Command: move process from wait queue to ready queue
    if(command == 1)
    {
        returnValue = unwaitFunction(PID);
    }
    //EOQUANTUM Command: move process from running queue to ready queue
    else if(command == 2)
    {
        returnValue = eoquantumFunction();
    }
    //EOLIFE Command: Move process from Running queue to Terminate queue
    else if(command == 3)
    {
        returnValue = eolifeFunction();
    }
    //WAIT Command: Move process from running queue to wait Queue
    else if(command == 4)
    {
        returnValue = waitFunction();
    }
    
    return returnValue;
}

